﻿using Lr.Core.Time;
using Lr.Core.Validation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using Xunit;

namespace Lr.Core.Tests._extensions_
{
    public class ObjectExtensionsTests
    {
        #region ToString
        [Fact]
        public void ToStringSafe_Returns_ToString_Correct_Value()
        {
            // Arrange
            var input = 50;
            var expected = input.ToString();

            // Act
            var actual = input.ToStringSafe();

            // Assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public void ToStringSafe_Returns_Null_String_Constant_When_Input_Object_Is_Null()
        {
            // Arrange
            var input = (object)null;
            var expected = ToStringHelper.NULL_STRING;

            // Act
            var actual = input.ToStringSafe();

            // Assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public void ToStringSafe_Selector_Returns_Correct_Value()
        {
            // Arrange
            var input = DateTime.UtcNow;
            var expected = input.ToShortDateString();

            // Act
            var actual = input.ToStringSafe(d => d.ToShortDateString());

            // Assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public void ToStringSafe_Selector_Return_Exception_String_Constant_When_Selector_Causes_Exception()
        {
            // Arrange
            var input = String.Empty;
            var expected = ToStringHelper.EXCEPTION_STRING.FormatWith(
                    typeof(IndexOutOfRangeException).Name
                );

            // Act
            var actual = input.ToStringSafe(s => s[5].ToString());

            // Assert
            Assert.Equal(expected, actual);
        }
        #endregion

        #region Debugging
        [Fact]
        public void ToDebugString_Returns_Correct_Value()
        {
            // Arrange
            var sut = new DateTimeRange(2014, 01, 01, 2015, 01, 01);
            var expected = "[DateTimeRange: Minimum='1/01/2014 0:00:00'; Maximum='1/01/2015 0:00:00']";

            // Act
            var actual = sut.ToDebugString();

            // Assert
            Assert.Equal(expected, actual);
        }

        [Fact]
        public void ToDebugString_With_Binding_Flags_Returns_Correct_Value()
        {
            // Arrange
            var sut = new DateTimeRange(2014, 01, 01, 2015, 01, 01);
            var expected = "[DateTimeRange]";

            // Act
            var actual = sut.ToDebugString(BindingFlags.NonPublic);

            // Assert
            Assert.Equal(expected, actual);
        }

        public class Person : IValidatable
        {
            private List<IValidationRule<Person>> Rules {get; set; }

            public string FirstName { get; set; }
            public string LastName { get; set; }
            private string InternalId { get; set; }

            private IRange<DateTime> Contract { get; set; }            

            public Person()
            {
                FirstName = "John";
                LastName = "Doe";
                InternalId = "JDOE@DOMAIN";
                Contract = new DateTimeRange(2009, 05, 05);

                Rules = new List<IValidationRule<Person>>();
                AddRule<Person>(new ValidationRule<Person>("FirstName can not be empty.", new string[]{ "FirstName" }, p => !String.IsNullOrEmpty(p.FirstName)));
            }

            public bool IsValid
            {
                get { return Validate().Count() == 0; }
            }

            public IEnumerable<ValidationResult> Validate()
            {
                foreach (var rule in Rules) {
                    if (!rule.Validate(this)) {
                        yield return new ValidationResult(rule.ErrorMessage, rule.Properties);
                    }
                }
            }

            public void AddRule<TObject>(IValidationRule<TObject> validationRule) where TObject : IValidatable
            {
                Rules.Add((IValidationRule<Person>)validationRule);
            }

            public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
            {
                return Validate();
            }
        }

        [Fact]
        public void ToDebugString_With_Complex_Binding_Flags_Returns_Correct_Value()
        {
            // Arrange
            var sut = new Person();
            var expected = "[Person: Rules='List<IValidationRule<Person>>'; FirstName='John'; LastName='Doe'; InternalId='JDOE@DOMAIN'; Contract='[DateTimeRange: Minimum='5/05/2009 0:00:00'; Maximum='31/12/9999 23:59:59']'; IsValid='True']";

            // Act
            var actual = sut.ToDebugString(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            // Assert
            Assert.Equal(expected, actual);
        }
        #endregion
    }
}
